    // Copyright (C) 2003  Davis E. King (davis@dlib.net), Miguel Grinberg
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_SOCKETS_KERNEL_2_CPp_
    #define DLIB_SOCKETS_KERNEL_2_CPp_

    #include "../platform.h"

    #ifdef DLIB_POSIX

    #include "sockets_kernel_2.h"
    #include <fcntl.h>
    #include "../set.h"
    #include <netinet/tcp.h>
    #include <string.h>

    namespace dlib
    {
    // ----------------------------------------------------------------------------------------

    #ifdef HPUX
        typedef int dsocklen_t;
    #else
        typedef socklen_t dsocklen_t;
    #endif

    // ----------------------------------------------------------------------------------------
    // stuff to ensure that the signal SIGPIPE is ignored before any connections are made
    // so that when a connection object is shutdown the program won't end on a broken pipe

        namespace sockets_kernel_2_mutex
        {
            mutex startup_lock;
        }

        void sockets_startup()
        {
            // mutex crap to make this function thread safe
            sockets_kernel_2_mutex::startup_lock.lock();
            static bool init = false;
            if (init == false)
            {
                init = true;
                signal( SIGPIPE, SIG_IGN);
            }
            sockets_kernel_2_mutex::startup_lock.unlock();
        }

    // ----------------------------------------------------------------------------------------

        // lookup functions

        int
        get_local_hostname (
            std::string& hostname
        )
        {
            try
            {
                char temp[MAXHOSTNAMELEN];

                if (gethostname(temp,MAXHOSTNAMELEN) == -1)
                {
                    return OTHER_ERROR;
                }
                // ensure that NUL is at the end of the string
                temp[MAXHOSTNAMELEN-1] = '\0';

                hostname = temp;
            }
            catch (...)
            {
                return OTHER_ERROR;
            }

            return 0;
        }

    // -----------------

    // cygwin currently doesn't support the getaddrinfo stuff
    #ifndef __CYGWIN__

        int
        hostname_to_ip (
            const std::string& hostname,
            std::string& ip,
            int n
        )
        {
            try
            {
                set<std::string>::kernel_1a sos;

                if (hostname.empty())
                    return OTHER_ERROR;

                addrinfo* result = 0;
                if (getaddrinfo(hostname.c_str(),0,0,&result))
                {
                    return OTHER_ERROR;
                }
                addrinfo* result_orig = result;

                // loop over all the addrinfo structures and add them to the set.  the reason for doing
                // this dumb crap is because different platforms return all kinds of weird garbage.  many
                // return the same ip multiple times, etc.
                while (result != 0)
                {
                    char temp[16];
                    inet_ntop (
                        AF_INET,
                        &((reinterpret_cast<sockaddr_in*>(result->ai_addr))->sin_addr),
                        temp,16
                        );

                    result = result->ai_next;

                    ip.assign(temp);
                    if (sos.is_member(ip) == false && ip != "0.0.0.0")
                        sos.add(ip);
                }

                freeaddrinfo(result_orig);

                // now return the nth unique ip address
                int i = 0;
                while (sos.move_next())
                {
                    if (i == n)
                    {
                        ip = sos.element();
                        return 0;
                    }
                    ++i;
                }

                return OTHER_ERROR;
            }
            catch (...)
            {
                return OTHER_ERROR;
            }
            return 0;
        }

    // -----------------

        int
        ip_to_hostname (
            const std::string& ip,
            std::string& hostname
        )
        {

            try
            {

                if (ip.empty())
                    return OTHER_ERROR;

                sockaddr_in sa;
                sa.sin_family = AF_INET;
                inet_pton(AF_INET,ip.c_str(),&sa.sin_addr);

                char temp[NI_MAXHOST];
                if ( getnameinfo (
                        reinterpret_cast<sockaddr*>(&sa),sizeof(sockaddr_in),
                        temp,
                        NI_MAXHOST,
                        0,
                        0,
                        NI_NAMEREQD
                    )
                )
                {
                    return OTHER_ERROR;
                }

                hostname.assign(temp);

            }
            catch (...)
            {
                return OTHER_ERROR;
            }
            return 0;
        }
    #else
        int
        hostname_to_ip (
            const std::string& hostname,
            std::string& ip,
            int n
        )
        {
            try
            {
                // lock this mutex since gethostbyname isn't really thread safe
                auto_mutex M(sockets_kernel_2_mutex::startup_lock);

                // if no hostname was given then return error
                if ( hostname.empty())
                    return OTHER_ERROR;

                hostent* address;
                address = gethostbyname(hostname.c_str());

                if (address == 0)
                {
                    return OTHER_ERROR;
                }

                // find the nth address
                in_addr* addr = reinterpret_cast<in_addr*>(address->h_addr_list[0]);
                for (int i = 1; i <= n; ++i)
                {
                    addr = reinterpret_cast<in_addr*>(address->h_addr_list[i]);

                    // if there is no nth address then return error
                    if (addr == 0)
                        return OTHER_ERROR;
                }

                char* resolved_ip = inet_ntoa(*addr);

                // check if inet_ntoa returned an error
                if (resolved_ip == NULL)
                {
                    return OTHER_ERROR;
                }

                ip.assign(resolved_ip);

            }
            catch(...)
            {
                return OTHER_ERROR;
            }

            return 0;
        }

    // -----------------

        int
        ip_to_hostname (
            const std::string& ip,
            std::string& hostname
        )
        {
            try
            {
                // lock this mutex since gethostbyaddr isn't really thread safe
                auto_mutex M(sockets_kernel_2_mutex::startup_lock);

                // if no ip was given then return error
                if (ip.empty())
                    return OTHER_ERROR;

                hostent* address;
                unsigned long ipnum = inet_addr(ip.c_str());

                // if inet_addr couldn't convert ip then return an error
                if (ipnum == INADDR_NONE)
                {
                    return OTHER_ERROR;
                }
                address = gethostbyaddr(reinterpret_cast<char*>(&ipnum),4,AF_INET);

                // check if gethostbyaddr returned an error
                if (address == 0)
                {
                    return OTHER_ERROR;
                }
                hostname.assign(address->h_name);

            }
            catch (...)
            {
                return OTHER_ERROR;
            }
            return 0;

        }

    #endif // __CYGWIN__

    // ----------------------------------------------------------------------------------------

        connection::
        connection(
            int sock,
            int foreign_port,
            const std::string& foreign_ip,
            int local_port,
            const std::string& local_ip
        ) :
            connection_socket(sock),
            connection_foreign_port(foreign_port),
            connection_foreign_ip(foreign_ip),
            connection_local_port(local_port),
            connection_local_ip(local_ip),
            sd(false),
            sdo(false),
            sdr(0)
        {}

    // ----------------------------------------------------------------------------------------

        int connection::
        disable_nagle()
        {
            int flag = 1;
            if(setsockopt( connection_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag) ))
            {
                return OTHER_ERROR;
            }

            return 0;
        }

    // ----------------------------------------------------------------------------------------

        long connection::
        write (
            const char* buf,
            long num
        )
        {
            const long old_num = num;
            long status;
            const long max_send_length = 1024*1024*100;
            while (num > 0)
            {
                // Make sure to cap the max value num can take on so that if it is
                // really large (it might be big on 64bit platforms) so that the OS
                // can't possibly get upset about it being large.
                const long length = std::min(max_send_length, num);
                if ( (status = ::send(connection_socket,buf,length,0)) <=0)
                {
                    // if send was interupted by a signal then restart it
                    if (errno == EINTR)
                    {
                        continue;
                    }
                    else
                    {
                        // check if shutdown or shutdown_outgoing have been called
                        if (sdo_called())
                            return SHUTDOWN;
                        else
                            return OTHER_ERROR;
                    }
                }
                num -= status;
                buf += status;
            }
            return old_num;
        }

    // ----------------------------------------------------------------------------------------

        long connection::
        read (
            char* buf,
            long num
        )
        {
            long status;
            const long max_recv_length = 1024*1024*100;
            while (true)
            {
                // Make sure to cap the max value num can take on so that if it is
                // really large (it might be big on 64bit platforms) so that the OS
                // can't possibly get upset about it being large.
                const long length = std::min(max_recv_length, num);
                status = recv(connection_socket,buf,length,0);
                if (status == -1)
                {
                    // if recv was interupted then try again
                    if (errno == EINTR)
                        continue;
                    else
                    {
                        if (sd_called())
                            return SHUTDOWN;
                        else
                            return OTHER_ERROR;
                    }
                }
                else if (status == 0 && sd_called())
                {
                    return SHUTDOWN;
                }

                return status;
            } // while (true)
        }
    // ----------------------------------------------------------------------------------------

        long connection::
        read (
            char* buf,
            long num,
            unsigned long timeout
        )
        {
            long status;
            const long max_recv_length = 1024*1024*100;

            if (readable(timeout) == false)
                return TIMEOUT;

            // Make sure to cap the max value num can take on so that if it is
            // really large (it might be big on 64bit platforms) so that the OS
            // can't possibly get upset about it being large.
            const long length = std::min(max_recv_length, num);
            status = recv(connection_socket,buf,length,0);
            if (status == -1)
            {
                // if recv was interupted then call this a timeout
                if (errno == EINTR)
                {
                    return TIMEOUT;
                }
                else
                {
                    if (sd_called())
                        return SHUTDOWN;
                    else
                        return OTHER_ERROR;
                }
            }
            else if (status == 0 && sd_called())
            {
                return SHUTDOWN;
            }

            return status;
        }

    // ----------------------------------------------------------------------------------------

        bool connection::
        readable (
            unsigned long timeout
        ) const
        {
            fd_set read_set;
            // initialize read_set
            FD_ZERO(&read_set);

            // add the listening socket to read_set
            FD_SET(connection_socket, &read_set);

            // setup a timeval structure
            timeval time_to_wait;
            time_to_wait.tv_sec = static_cast<long>(timeout/1000);
            time_to_wait.tv_usec = static_cast<long>((timeout%1000)*1000);

            // wait on select
            int status = select(connection_socket+1,&read_set,0,0,&time_to_wait);

            // if select timed out or there was an error
            if (status <= 0)
                return false;

            // socket is ready to be read
            return true;
        }

    // ----------------------------------------------------------------------------------------

        connection::
        ~connection (
        )
        {
            while (true)
            {
                int status = ::close(connection_socket);
                if (status == -1 && errno == EINTR)
                    continue;
                break;
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // listener object
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        listener::
        listener(
            int sock,
            int port,
            const std::string& ip
        ) :
            listening_socket(sock),
            listening_port(port),
            listening_ip(ip),
            inaddr_any(listening_ip.empty())
        {}

    // ----------------------------------------------------------------------------------------

        listener::
        ~listener (
        )
        {
            while (true)
            {
                int status = ::close(listening_socket);
                if (status == -1 && errno == EINTR)
                    continue;
                break;
            }
        }

    // ----------------------------------------------------------------------------------------

        int listener::
        accept (
            std::unique_ptr<connection>& new_connection,
            unsigned long timeout
        )
        {
            new_connection.reset(0);
            connection* con;
            int status = this->accept(con, timeout);

            if (status == 0)
                new_connection.reset(con);

            return status;
        }

    // ----------------------------------------------------------------------------------------

        int listener::
        accept (
            connection*& new_connection,
            unsigned long timeout
        )
        {
            int incoming;
            sockaddr_in incomingAddr;
            dsocklen_t length = sizeof(sockaddr_in);

            // implement timeout with select if timeout is > 0
            if (timeout > 0)
            {

                fd_set read_set;
                // initialize read_set
                FD_ZERO(&read_set);

                // add the listening socket to read_set
                FD_SET(listening_socket, &read_set);

                timeval time_to_wait;

                // loop on select so if its interupted then we can start it again
                while (true)
                {

                    // setup a timeval structure
                    time_to_wait.tv_sec = static_cast<long>(timeout/1000);
                    time_to_wait.tv_usec = static_cast<long>((timeout%1000)*1000);

                    // wait on select
                    int status = select(listening_socket+1,&read_set,0,0,&time_to_wait);

                    // if select timed out
                    if (status == 0)
                        return TIMEOUT;

                    // if select returned an error
                    if (status == -1)
                    {
                        // if select was interupted or the connection was aborted
                        // then go back to select
                        if (errno == EINTR ||
                            errno == ECONNABORTED ||
    #ifdef EPROTO
                            errno == EPROTO ||
    #endif
                            errno == ECONNRESET
                            )
                        {
                            continue;
                        }
                        else
                        {
                            return OTHER_ERROR;
                        }
                    }

                    // accept the new connection
                    incoming=::accept (
                        listening_socket,
                        reinterpret_cast<sockaddr*>(&incomingAddr),
                        &length
                        );

                    // if there was an error return OTHER_ERROR
                    if ( incoming == -1 )
                    {
                        // if accept was interupted then go back to accept
                        if (errno == EINTR ||
                            errno == ECONNABORTED ||
    #ifdef EPROTO
                            errno == EPROTO ||
    #endif
                            errno == ECONNRESET
                            )
                        {
                            continue;
                        }
                        else
                        {
                            return OTHER_ERROR;
                        }
                    }

                    // if there were no errors then quit loop
                    break;

                }

            }
            // else if there is no time out then just go into accept
            else
            {
                while (true)
                {
                    // call accept to get a new connection
                    incoming=::accept (
                        listening_socket,
                        reinterpret_cast<sockaddr*>(&incomingAddr),
                        &length
                        );

                    // if there was an error return OTHER_ERROR
                    if ( incoming == -1 )
                    {
                        // if accept was interupted then go back to accept
                        if (errno == EINTR ||
                            errno == ECONNABORTED ||
    #ifdef EPROTO
                            errno == EPROTO ||
    #endif
                            errno == ECONNRESET
                            )
                        {
                            continue;
                        }
                        else
                        {
                            return OTHER_ERROR;
                        }
                    }
                    break;
                }

            }

            // get the port of the foreign host into foreign_port
            int foreign_port = ntohs(incomingAddr.sin_port);

            // get the IP of the foreign host into foreign_ip
            char foreign_ip[16];
            inet_ntop(AF_INET,&incomingAddr.sin_addr,foreign_ip,16);

            // get the local ip for this connection into local_ip
            char temp_local_ip[16];
            std::string local_ip;
            if (inaddr_any == true)
            {
                sockaddr_in local_info;
                length = sizeof(sockaddr_in);
                // get the local sockaddr_in structure associated with this new connection
                if ( getsockname (
                        incoming,
                        reinterpret_cast<sockaddr*>(&local_info),
                        &length
                    ) == -1
                )
                {   // an error occurred
                    while (true)
                    {
                        int status = ::close(incoming);
                        if (status == -1 && errno == EINTR)
                            continue;
                        break;
                    }
                    return OTHER_ERROR;
                }
                local_ip = const_cast<char*> (
                    inet_ntop(AF_INET,&local_info.sin_addr,temp_local_ip,16)
                    );
            }
            else
            {
                local_ip = listening_ip;
            }

            // set the SO_OOBINLINE option
            int flag_value = 1;
            if (setsockopt(incoming,SOL_SOCKET,SO_OOBINLINE,reinterpret_cast<const void*>(&flag_value),sizeof(int)))
            {
                while (true)
                {
                    int status = ::close(incoming);
                    if (status == -1 && errno == EINTR)
                        continue;
                    break;
                }
                return OTHER_ERROR;
            }

            // make a new connection object for this new connection
            try
            {
                new_connection = new connection (
                                        incoming,
                                        foreign_port,
                                        foreign_ip,
                                        listening_port,
                                        local_ip
                                    );
            }
            catch (...)
            {
                while (true)
                {
                    int status = ::close(incoming);
                    if (status == -1 && errno == EINTR)
                        continue;
                    break;
                }
                return OTHER_ERROR;
            }

            return 0;
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // socket creation functions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        static void
        close_socket (
            int sock
        )
        /*!
            requires
                - sock == a socket
            ensures
                - sock has been closed
        !*/
        {
            while (true)
            {
                int status = ::close(sock);
                if (status == -1 && errno == EINTR)
                    continue;
                break;
            }
        }

    // ----------------------------------------------------------------------------------------

        int create_listener (
            std::unique_ptr<listener>& new_listener,
            unsigned short port,
            const std::string& ip
        )
        {
            new_listener.reset();
            listener* temp;
            int status = create_listener(temp,port,ip);

            if (status == 0)
                new_listener.reset(temp);

            return status;
        }

        int create_listener (
            listener*& new_listener,
            unsigned short port,
            const std::string& ip
        )
        {
            sockets_startup();

            sockaddr_in sa;  // local socket structure
            memset(&sa,'\0',sizeof(sockaddr_in)); // initialize sa

            int sock = socket (AF_INET, SOCK_STREAM, 0);  // get a new socket

            // if socket() returned an error then return OTHER_ERROR
            if (sock == -1)
            {
                return OTHER_ERROR;
            }

            // set the local socket structure
            sa.sin_family = AF_INET;
            sa.sin_port = htons(port);
            if (ip.empty())
            {
                // if the listener should listen on any IP
                sa.sin_addr.s_addr = htons(INADDR_ANY);
            }
            else
            {
                // if there is a specific ip to listen on
                sa.sin_addr.s_addr = inet_addr(ip.c_str());

                // if inet_addr couldn't convert the ip then return an error
                if ( sa.sin_addr.s_addr == ( in_addr_t)(-1))
                {
                    close_socket(sock);
                    return OTHER_ERROR;
                }
            }

            // set the SO_REUSEADDR option
            int flag_value = 1;
            if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,reinterpret_cast<const void*>(&flag_value),sizeof(int)))
            {
                close_socket(sock);
                return OTHER_ERROR;
            }

            // bind the new socket to the requested port and ip
            if (bind(sock,reinterpret_cast<sockaddr*>(&sa),sizeof(sockaddr_in)) == -1)
            {   // if there was an error
                close_socket(sock);

                // if the port is already bound then return PORTINUSE
                if (errno == EADDRINUSE)
                    return PORTINUSE;
                else
                    return OTHER_ERROR;
            }

            // tell the new socket to listen
            if ( listen(sock,SOMAXCONN) == -1)
            {
                // if there was an error return OTHER_ERROR
                close_socket(sock);

                // if the port is already bound then return PORTINUSE
                if (errno == EADDRINUSE)
                    return PORTINUSE;
                else
                    return OTHER_ERROR;
            }

            // determine the used local port if necessary
            if (port == 0)
            {
                sockaddr_in local_info;
                dsocklen_t length = sizeof(sockaddr_in);
                if ( getsockname(
                    sock,
                    reinterpret_cast<sockaddr*>(&local_info),
                    &length
                    ) == -1)
                {
                    close_socket(sock);
                    return OTHER_ERROR;
                }
                port = ntohs(local_info.sin_port);
            }

            // initialize a listener object on the heap with the new socket
            try { new_listener = new listener(sock,port,ip); }
            catch(...) { close_socket(sock); return OTHER_ERROR; }

            return 0;
        }

    // ----------------------------------------------------------------------------------------

        int create_connection (
            std::unique_ptr<connection>& new_connection,
            unsigned short foreign_port,
            const std::string& foreign_ip,
            unsigned short local_port,
            const std::string& local_ip
        )
        {
            new_connection.reset();
            connection* temp;
            int status = create_connection(temp,foreign_port, foreign_ip, local_port, local_ip);

            if (status == 0)
                new_connection.reset(temp);

            return status;
        }

        int
        create_connection (
            connection*& new_connection,
            unsigned short foreign_port,
            const std::string& foreign_ip,
            unsigned short local_port,
            const std::string& local_ip
        )
        {
            sockets_startup();

            sockaddr_in local_sa;  // local socket structure
            sockaddr_in foreign_sa;  // foreign socket structure
            memset(&local_sa,'\0',sizeof(sockaddr_in)); // initialize local_sa
            memset(&foreign_sa,'\0',sizeof(sockaddr_in)); // initialize foreign_sa

            dsocklen_t length;

            int sock = socket (AF_INET, SOCK_STREAM, 0);  // get a new socket

            // if socket() returned an error then return OTHER_ERROR
            if (sock == -1 )
            {
                return OTHER_ERROR;
            }

            // set the foreign socket structure
            foreign_sa.sin_family = AF_INET;
            foreign_sa.sin_port = htons(foreign_port);
            foreign_sa.sin_addr.s_addr = inet_addr(foreign_ip.c_str());

            // if inet_addr couldn't convert the ip then return an error
            if ( foreign_sa.sin_addr.s_addr == ( in_addr_t)(-1))
            {
                close_socket(sock);
                return OTHER_ERROR;
            }

            // set up the local socket structure
            local_sa.sin_family = AF_INET;

            // set the local port
            local_sa.sin_port = htons(local_port);

            // set the local ip
            if (local_ip.empty())
            {
                // if the listener should listen on any IP
                local_sa.sin_addr.s_addr = htons(INADDR_ANY);
            }
            else
            {
                // if there is a specific ip to listen on
                local_sa.sin_addr.s_addr = inet_addr(local_ip.c_str());

                // if inet_addr couldn't convert the ip then return an error
                if ( local_sa.sin_addr.s_addr == ( in_addr_t)(-1))
                {
                    close_socket(sock);
                    return OTHER_ERROR;
                }
            }

            // bind the new socket to the requested local port and local ip
            if ( bind(sock,reinterpret_cast<sockaddr*>(&local_sa),sizeof(sockaddr_in)) == -1)
            {   // if there was an error
                close_socket(sock);

                // if the port is already bound then return PORTINUSE
                if (errno == EADDRINUSE)
                    return PORTINUSE;
                else
                    return OTHER_ERROR;
            }

            // connect the socket
            if ( connect (
                    sock,
                    reinterpret_cast<sockaddr*>(&foreign_sa),
                    sizeof(sockaddr_in)
                ) == -1
            )
            {
                close_socket(sock);
                // if the port is already bound then return PORTINUSE
                if (errno == EADDRINUSE)
                    return PORTINUSE;
                else
                    return OTHER_ERROR;
            }

            // determine the local port and IP and store them in used_local_ip
            // and used_local_port
            int used_local_port;
            char temp_used_local_ip[16];
            std::string used_local_ip;
            sockaddr_in local_info;

            // determine the port
            if (local_port == 0)
            {
                length = sizeof(sockaddr_in);
                if ( getsockname(
                        sock,
                        reinterpret_cast<sockaddr*>(&local_info),
                        &length
                    ) == -1)
                {
                    close_socket(sock);
                    return OTHER_ERROR;
                }
                used_local_port = ntohs(local_info.sin_port);
            }
            else
            {
                used_local_port = local_port;
            }

            // determine the ip
            if (local_ip.empty())
            {
                // if local_port is not 0 then we must fill the local_info structure
                if (local_port != 0)
                {
                    length = sizeof(sockaddr_in);
                    if ( getsockname (
                            sock,
                            reinterpret_cast<sockaddr*>(&local_info),
                            &length
                        ) == -1
                    )
                    {
                        close_socket(sock);
                        return OTHER_ERROR;
                    }
                }
                used_local_ip = inet_ntop(AF_INET,&local_info.sin_addr,temp_used_local_ip,16);
            }
            else
            {
                used_local_ip = local_ip;
            }

            // set the SO_OOBINLINE option
            int flag_value = 1;
            if (setsockopt(sock,SOL_SOCKET,SO_OOBINLINE,reinterpret_cast<const void*>(&flag_value),sizeof(int)))
            {
                close_socket(sock);
                return OTHER_ERROR;
            }

            // initialize a connection object on the heap with the new socket
            try
            {
                new_connection = new connection (
                                        sock,
                                        foreign_port,
                                        foreign_ip,
                                        used_local_port,
                                        used_local_ip
                                    );
            }
            catch(...) {close_socket(sock);  return OTHER_ERROR; }

            return 0;
        }

    // ----------------------------------------------------------------------------------------

    }

    #endif // DLIB_POSIX

    #endif // DLIB_SOCKETS_KERNEL_2_CPp_